package com.omt.tarjimdinek.conversation;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.omt.tarjimdinek.util.PrimeFacesUtil;

/**
 * Responsible for creating/binding the entity manager used during the conversation.
 */
public class EntityManagerConversationListener implements ConversationListener {
    private static final Logger log = Logger.getLogger(EntityManagerConversationListener.class);

    private EntityManagerFactory entityManagerFactory;

    public EntityManagerConversationListener(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    @Override
    public void conversationCreated(Conversation conversation) {
    }

    @Override
    public void conversationResuming(Conversation conversation, HttpServletRequest request) {
        ConversationContext<?> ctx = conversation.getCurrentContext();

        if (ctx.useConversationEntityManager()) {
            if (conversation.getEntityManager() == null) {
                EntityManager em = entityManagerFactory.createEntityManager();
                conversation.setEntityManager(em);
                if (log.isDebugEnabled()) {
                    log.debug("conv. " + conversation.getId() + " resuming: conversation's entityManager created: " + em.hashCode());
                }
            }

            if (PrimeFacesUtil.isAjax(request)) {
                String componentId = request.getParameter("javax.faces.source");
                if (componentId != null && ctx.ignoreUseConversationEntityManager(componentId)) {
                    // _HACK_ as it is a tricky subject
                    if (log.isDebugEnabled()) {
                        log.debug("Skip conversation's entityManager binding for ajax request coming from id: " + componentId);
                    }
                    return;
                }
            }

            bindEntityManager(conversation.getEntityManager());
        }
    }

    @Override
    public void conversationPausing(Conversation conversation) {
        EntityManager em = conversation.getEntityManager();
        if (em != null) {
            boolean needToReleaseConnection = false;

            if (unbindEntityManager(conversation.getEntityManager())) {
                needToReleaseConnection = true;
            }

            if (!conversation.isEntityManagerStillNeeded()) {
                em.close();
                conversation.setEntityManager(null);
                if (log.isDebugEnabled()) {
                    log.debug("conv. " + conversation.getId() + " pausing: conversation's entityManager closed: " + em.hashCode());
                }
            } else if (needToReleaseConnection) {
                // _HACK_ as we depend on Hibernate
                // Note: normally we should not have to do this as we have configured hibernate
                // with hibernate.connection.release_mode=after_transaction
                // But we load some lazy data non transactionnally from the view...
                Session session = em.unwrap(Session.class);
                session.disconnect(); // will be reconnected automatically as needed.
            }
        }
    }

    @Override
    public void conversationEnding(Conversation conversation) {
        EntityManager em = conversation.getEntityManager();
        if (em != null) {
            unbindEntityManager(em);
            em.close();
            conversation.setEntityManager(null); // not really required, but just for clarity.

            if (log.isDebugEnabled()) {
                log.debug("conv. " + conversation.getId() + " ending: conversation's entityManager closed: " + em.hashCode());
            }
        }
    }

    // --------------------------------------------
    // Extended Persistence Context related
    // (same code as in spring web flow)
    // --------------------------------------------

    private void bindEntityManager(EntityManager em) {
        if (log.isDebugEnabled()) {
            log.debug("Binding conversation's entityManager: " + em.hashCode());
        }
        TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(em));
    }

    private boolean unbindEntityManager(EntityManager em) {
        if (TransactionSynchronizationManager.hasResource(entityManagerFactory)) {
            TransactionSynchronizationManager.unbindResource(entityManagerFactory);
            return true;
        }
        return false;
    }
}
